package com.android.tony.defenselib.hook;

import java.lang.reflect.Method;

import android.content.Context;
import android.util.Log;

import static android.os.Build.VERSION.SDK_INT;

public class Reflection {
  private static final String TAG = "Reflection";

  private static Object sVmRuntime;

  private static Method setHiddenApiExemptions;

  static {
    try {
      Method forName = Class.class.getDeclaredMethod("forName", String.class);
      Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);

      Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, "dalvik.system.VMRuntime");
      Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null);
      setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[] {String[].class});
      sVmRuntime = getRuntime.invoke(null);
    } catch (Throwable e) {
      Log.e(TAG, "reflect bootstrap failed");
    }
  }

  private static int UNKNOWN = -9999;

  private static final int ERROR_SET_APPLICATION_FAILED = -20;

  private static final int ERROR_EXEMPT_FAILED = -21;

  private static int unsealed = UNKNOWN;

  public static int unseal(Context context) {
    if (SDK_INT < 28) {
      // Below Android P, ignore
      return 0;
    }

    // try exempt API first.
    if (exemptAll()) {
      return 0;
    } else {
      return ERROR_EXEMPT_FAILED;
    }
  }

  /**
   * make the method exempted from hidden API check.
   *
   * @param method the method signature prefix.
   * @return true if success.
   */
  public static boolean exempt(String method) {
    return exempt(new String[] {method});
  }

  /**
   * make specific methods exempted from hidden API check.
   *
   * @param methods the method signature prefix, such as "Ldalvik/system", "Landroid" or even "L"
   * @return true if success
   */
  public static boolean exempt(String... methods) {
    if (sVmRuntime == null || setHiddenApiExemptions == null) {
      return false;
    }

    try {
      setHiddenApiExemptions.invoke(sVmRuntime, new Object[] {methods});
      return true;
    } catch (Throwable e) {
      return false;
    }
  }

  /**
   * Make all hidden API exempted.
   *
   * @return true if success.
   */
  public static boolean exemptAll() {
    return exempt(new String[] {"L"});
  }
}
